﻿using System;
using System.IO;
using System.Linq;

namespace QuickMenus
{
    /// <summary>
    /// Encapsulates displaying a list of options to take, as well as acting on user selections.
    /// </summary>
    public class Menu : MenuItem
    {
        private MenuTrie entries;

       /// <summary>
       /// Non-identifying text that will inform a user that they can issue a command.
       /// </summary>
        public const string DEFAULT_PROMPT = ">";

        /// <summary>
        /// A string constant that can be used to identify moving up in a menu structure.
        /// </summary>
        public const string DEFAULT_EXIT_ALIAS = "Back";

        /// <summary>
        /// A string constant that can be used to identify leaving a program.
        /// </summary>
        public const string TOP_EXIT_ALIAS = "Exit";

        /// <summary>
        /// Text that will be displayed, informing the user that they can now issue a command.
        /// </summary>
        public string Prompt { get; private set; }

        private TextWriter output;
        private TextReader input;

        /// <summary>
        /// Encapsulates whether or not commands should be treated as case sensitive.
        /// </summary>
        /// <returns>Whether or not the commands on this menu are currently getting treated as case sensitive.</returns>
        public bool CaseSensitive { get; set; }

        /// <summary>
        /// Constructs an instance of a <see cref="Menu"/>
        /// </summary>
        /// <param name="name">Text that will identify the menu.</param>
        /// <param name="prompt">The text that will be used to inform a user that they can issue a command <seealso cref="Prompt"/></param>
        /// <param name="exitAlias">All menus must have at least one option, which is the option to leave that menu. This text identifies how to leave that menu.</param>
        /// <param name="output">The means of writing this menu.</param>
        /// <param name="input">The means of reading input to this menu.</param>
        public Menu(string name, string prompt = DEFAULT_PROMPT, string exitAlias = DEFAULT_EXIT_ALIAS, TextWriter output = null, TextReader input = null)
            : base(name)
        {
            this.output = output;
            if (null == this.output)
            {
                this.output = Console.Out;
            }

            this.input = input;
            if (null == this.input)
            {
                this.input = Console.In;
            }

            CaseSensitive = false;
            Prompt = prompt;

            entries = new MenuTrie();

            var aliasLister = new ListAllItem(this, "Show Options");
            entries.Put("?", aliasLister);
            entries.Put("help", aliasLister);
            entries.Put("show options", aliasLister);

            entries.Put(exitAlias, ExitItem.DEFAULT_INSTANCE.Value);
        }

        /// <summary>
        /// A function that renders this menu. <seealso cref="DisplayMenu(string[])"/>
        /// </summary>
        /// <returns>A means of rendering this menu.</returns>
        public override Func<string[], int> Operation
        {
            get
            {
                return (t) =>
                {
                    DisplayMenu();
                    return 0;
                };
            }
        }

        /// <summary>
        /// Appends the menu with an additional option.
        /// </summary>
        /// <param name="aliases">Commands that the user can use to access the menu item.</param>
        /// <param name="entry">The item that will be displayed in the menu.</param>
        public void AddEntry(string[] aliases, MenuItem entry)
        {
            foreach (var alias in aliases)
            {
                var copy = alias;
                if(!CaseSensitive)
                {
                    copy = copy.ToLower();
                }
                entries.Put(copy, entry);
            }
        }
                /// <summary>
        /// Seeks and removes all aliases associated with a MenuItem. Freeing any resources associated with that item.
        /// </summary>
        /// <param name="entry">The entry in the menu which should be removed.</param>
        public void RemoveEntry(MenuItem entry)
        {
            //TODO
        }

        /// <summary>
        /// Unlinks a command from an item in a menu.
        /// </summary>
        /// <param name="alias"></param>
        public void RemoveAlias(string alias)
        {
            entries.Remove(alias);
        }

        /// <summary>
        /// Handles displaying a menu and handling user interaction.
        /// </summary>
        public void DisplayMenu()
        {
            MenuItem selected = null;
            do
            {
                output.WriteLine(DisplayName);
                foreach(var item in entries.Values)
                {
                    output.WriteLine("\t{0}", item.DisplayName);
                }
                output.Write(Prompt);
                var response = input.ReadLine();
                var responseTokens = response.Split(new[] { ' ', '\t' });
                var candidates = entries.GetCandidates(responseTokens.FirstOrDefault());
                
                if(candidates.Any())
                {
                    if(candidates.Count() > 1)
                    {
                        output.WriteLine("Options:");
                        foreach(var candidate in candidates)
                        {
                            output.WriteLine("\t{0}", candidate);
                        }
                    }
                    else
                    {
                        selected = entries.Get(candidates.Single());
                        var result = selected.Operation(responseTokens);
                        Console.WriteLine("{0} exited with a status of: {1}", selected.DisplayName, result);
                    }
                }
                else
                {
                    output.WriteLine("Unable to process command: \"{0}\"", response);
                }
            } while (!(selected is ExitItem));
        }

        /// <summary>
        /// A simple action which lists all of the aliases that are available to a user at this level in the menu structure.
        /// </summary>
        private class ListAllItem : MenuItem
        {
            private Menu parent;
            public const string DEFAULT_NAME = "Show Options";
            public ListAllItem(Menu parent, string name = DEFAULT_NAME)
                : base(name)
            {
                this.parent = parent;
            }

            public override Func<string[], int> Operation
            {
                get
                {
                    return (string[] args) =>
                    {
                        parent.output.WriteLine("Available Commands:");
                        var allKeys = parent.entries.Keys.OrderBy((k) => k.First());
                        foreach(var alias in parent.entries.Keys)
                        {
                            parent.output.WriteLine("\t{0} -> {1}", alias, parent.entries.Get(alias).DisplayName);
                        }
                        return 0;
                    };
                }
            }
        }
    }
}
